﻿using System;
using OPCDiffCore;
using OPCDiffCore.Relationship;
using OPCDiffCore.Text;
using OPCDiffCore.Zip;
using System.IO;
using System.IO.Packaging;
using System.Text;

namespace OPCDiffCmd.Cartridge
{
    internal sealed class HTMLCartridge : IOutputCartridge
    {
        public void BuildOutput(OPCDiffCore.PackageDiff packageDiff)
        {
            Console.WriteLine("Start building HTML report ...");
            StringBuilder sb = new StringBuilder();
            if (packageDiff.CountPartDiff == 0)
            {
                // No changes summary web page
                sb.Append(GetHTMLHeader(null));
                sb.Append(GetNoChangesSummaryContent());
                sb.Append(GetHTMLFooter());
                WriteWebPage("index.html", sb.ToString());
            }
            else
            {
                // Generate the summary page
                // No changes summary web page
                sb.Append(GetHTMLHeader(null));
                sb.Append(GetChangesSummaryContent(packageDiff));
                sb.Append(GetHTMLFooter());
                WriteWebPage("index.html", sb.ToString());

                foreach (PartDiff partDiff in packageDiff.PartDiffs)
                {
                    sb = new StringBuilder();
                    sb.Append(GetHTMLHeader(partDiff.PartUri));
                    sb.Append(GetChangesContent(partDiff));
                    sb.Append(GetHTMLFooter());
                    WriteWebPage(GetHTMLFilenameFromURI(partDiff.PartUri), sb.ToString());
                }
            }
            Console.WriteLine("The HTML Diff report is available !");
        }

        private void WriteWebPage(string filename, string content)
        {
            string dir = Path.GetDirectoryName(filename);
            if (!Directory.Exists(dir) && !String.IsNullOrEmpty(dir))
                Directory.CreateDirectory(dir);
            File.WriteAllText(filename, content, Encoding.UTF8);
        }

        /// <summary>
        /// Get valid web page filename from the specified URI.
        /// </summary>
        private string GetHTMLFilenameFromURI(Uri pageUri)
        {
            // Build correct output filename
            StringBuilder sb = new StringBuilder();
            if (pageUri.ToString().StartsWith("/"))
                sb.Append(pageUri.ToString().Substring(1));
            else
                sb.Append(pageUri.ToString());

            if (!pageUri.ToString().EndsWith(".html"))
                sb.Append(".html");

            return sb.ToString();
        }

        private string GetNoChangesSummaryContent()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("<h1>No changes</h1>");
            return sb.ToString();
        }

        private string GetChangesSummaryContent(PackageDiff packageDiff)
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("<ul>");
            foreach (PartDiff partDiff in packageDiff.PartDiffs)
            {
                string style = "";
                if (partDiff.DiffType != DiffType.Unchanged)
                {
                    switch (partDiff.DiffType)
                    {
                        case DiffType.Added:
                            style = "style=\"background-color:green\"";
                            break;

                        case DiffType.Modified:
                            style = "style=\"background-color:yellow\"";
                            break;

                        case DiffType.Removed:
                            style = "style=\"background-color:red\"";
                            break;
                    }
                }

                sb.AppendFormat("<li {3}><a href=\"{0}\">'{1}' - {2}</a></li>", GetHTMLFilenameFromURI(partDiff.PartUri),
                    partDiff.PartUri.ToString(), partDiff.DiffType.ToString().ToUpper(), style);
            }
            sb.AppendLine("</ul>");

            return sb.ToString();
        }

        /// <summary>
        /// Gets the HTML content for the specified part.
        /// </summary>
        private string GetChangesContent(PartDiff partDiff)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("<h1>Changes for part '{0}'</h1>\n", partDiff.PartUri.ToString());

            switch (partDiff.DiffType)
            {
                case DiffType.Modified:
                    if (PackUriHelper.IsRelationshipPartUri(partDiff.PartUri))
                        sb.AppendLine(String.Format("<h2>Relationship part '{0}' MODIFIED</h2>", partDiff.PartUri.ToString()));
                    else
                        sb.AppendLine(String.Format("<h2>Part '{0}' MODIFIED</h2>",
                            partDiff.PartUri));

                    sb.Append("<ul>");
                    // Display modified element
                    foreach (DiffElement diffElement in partDiff)
                    {
                        sb.AppendLine("<li>" + diffElement.ToString());

                        // Display specific change detail
                        switch (diffElement.Type)
                        {
                            case DiffElementType.TextContent:
                                LineChange lineChange = (LineChange)diffElement.Extra;
                                foreach (InternalLineChange intLineChange in lineChange.Changes)
                                    sb.AppendLine(
                                        String.Format(": Line {0} from {1} to {2}: {3} -> {4}<li/>",
                                        intLineChange.DiffType,
                                        intLineChange.Start,
                                        intLineChange.End,
                                        System.Web.HttpUtility.HtmlEncode(intLineChange.ContentOriginal),
                                        intLineChange.ContentDiff));
                                break;

                            case DiffElementType.Relationship:
                                RelationshipChange relChange = (RelationshipChange)diffElement.Extra;
                                foreach (InternalRelationshipChange intRelChange in relChange.ChangeList)
                                {
                                    string output = null;
                                    switch (relChange.DiffType)
                                    {
                                        case DiffType.Modified:
                                            output = String.Format(": Property '{0}' modified: '{1}' -> '{2}'</li>",
                                                intRelChange.ChangedElement,
                                                intRelChange.OldValue,
                                                intRelChange.NewValue);
                                            break;

                                        case DiffType.Added:
                                            output = String.Format(": Property '{0}' added: '{1}'</li>",
                                                        intRelChange.ChangedElement,
                                                        intRelChange.NewValue);
                                            break;

                                        case DiffType.Removed:
                                            output = String.Format(": Property '{0}' removed: '{1}'</li>",
                                                intRelChange.ChangedElement,
                                                intRelChange.OldValue);
                                            break;

                                    }
                                    sb.AppendLine(output);
                                }
                                break;

                            case DiffElementType.BinaryContent:
                                // TODO
                                break;

                            case DiffElementType.XmlContent:
                                // TODO
                                break;

                            default:
                                // TODO
                                break;
                        }
                    }
                    sb.Append("</ul>");
                    break;

                case DiffType.Removed:
                    if (PackUriHelper.IsRelationshipPartUri(partDiff.PartUri))
                        sb.AppendLine(String.Format("Relationship part {0} REMOVED.", partDiff.PartUri.ToString()));
                    else
                        sb.AppendLine(String.Format("Part {0} REMOVED.", partDiff.PartUri.ToString()));
                    break;

                case DiffType.Added:
                    if (PackUriHelper.IsRelationshipPartUri(partDiff.PartUri))
                        sb.AppendLine(String.Format("Relationship part {0} ADDED.", partDiff.PartUri.ToString()));
                    else
                        sb.AppendLine(String.Format("Part {0} ADDED.", partDiff.PartUri.ToString()));
                    break;

                default:
                    throw new InvalidProgramException("Must not have 'Unchanged' change !");
            }
            return sb.ToString();
        }

        private string GetHTMLHeader(Uri partUri)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<html>\n<head><title>");
            if (partUri == null)
                sb.Append("OPCDiff Summary");
            else
                sb.AppendFormat("OPCDiff - Part '{0}'", partUri.ToString());
            sb.Append("</title></head>\n<body>\n");
            if (partUri == null)
                sb.Append("<h1>OPCDiff Summary</h1>");

            return sb.ToString();
        }

        private string GetHTMLFooter()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("</body>");
            sb.Append("</html>");

            return sb.ToString();
        }
    }
}